import {
  AngleUnit,
  angleUnits,
  LengthUnit,
  lengthUnits,
  parameterObjectExprToLabel,
  ParameterObjectType,
  UnitInfo,
  useCommandManager,
  useUnit,
} from '@amcax/base';
import { useNumberWithUnit } from '@app-base/common/hooks/unit';
import { useParameterLinkedTable } from '@app-base/common/hooks/useParameterLinkedTable';
import { ReactComponent as FxSvgComponent } from './fx.svg';

import { docMgr, unitManager, userEvents } from '@app-base/module';
import { Button, InputNumberProps } from 'antd';
import _ from 'lodash';
import { useEffect, useId, useMemo, useState } from 'react';
import { ExpressionDialog } from './ExpressionDialog';
import { LinkedParameterInput } from './LinkedParameterInput';
import { getUnitForExpression } from './utils';
import { ExpressionValue } from '@app-base/types';

const VIEW_PRECISION = 3;
const INPUT_PRECISION = 6;

function calculateNumber(expression: string) {
  // 允许的字符集合
  const allowedChars = /^[0-9+\-*/().\s]*$/;
  // 检查表达式是否只包含允许的字符
  if (!allowedChars.test(expression)) {
    return expression;
  }
  try {
    // 计算表达式的值
    const result = eval(expression);
    return typeof result === 'number' ? result : expression;
  } catch {
    return expression;
  }
}

export const useFormatInput = ({
  inputType,
  outerValue,
  onChange,
  parameterName,
  inputProps,
  onDialogVisibleChange,
}: {
  inputType: ParameterObjectType;
  outerValue: ExpressionValue;
  onChange?: (val: ExpressionValue) => void;
  inputProps?: InputNumberProps;
  parameterName?: string;
  onDialogVisibleChange?: (visible: boolean) => void;
}) => {
  const hasUnit = [
    ParameterObjectType.Length,
    ParameterObjectType.Angle,
  ].includes(inputType);
  const valueIsExpression = typeof outerValue === 'string';

  const getUnitParams = (
    inputType === ParameterObjectType.Length
      ? unitManager.getLengthUnitParams
      : unitManager.getAngleUnitParams
  ).bind(unitManager) as (unit?: string) => UnitInfo<string>;

  const { lengthUnit, angleUnit } = useUnit();

  const [showFunctionDialog, setShowFunctionDialog] = useState(false);

  const currentUnit = getUnitParams(
    inputType === ParameterObjectType.Length ? lengthUnit : angleUnit,
  );

  const unitList =
    inputType === ParameterObjectType.Length ? lengthUnits : angleUnits;

  const [isFocus, setIsFocus] = useState(false);

  const { getNumWithPrecision } = useNumberWithUnit();

  const { currentCmdProps } = useCommandManager();

  const { dxid } = currentCmdProps ?? {};
  const formatter = (
    val: ExpressionValue,
    includesUnit = true,
    precision?: number,
  ) => {
    const _val = val ?? 0;
    if (typeof _val === 'string') {
      return [
        parameterObjectExprToLabel(_val, docMgr),
        getUnitForExpression(inputType)?.name,
      ]
        .filter(Boolean)
        .join(' ');
    }

    const getNum = (num: number) => {
      return getNumWithPrecision(num, precision, 6, VIEW_PRECISION);
    };

    if (!hasUnit) {
      return getNum(_val).toString();
    }

    if (!includesUnit) {
      return getNum(_val / currentUnit.multiple).toString();
    }

    return `${getNum(_val / currentUnit.multiple)} ${currentUnit.name}`;
  };

  const formatterWithPrecision = (val: number | string, includesUnit = true) =>
    formatter(val, includesUnit, isFocus ? INPUT_PRECISION : VIEW_PRECISION);

  useEffect(() => {
    setValue('');
  }, [inputType]);

  useEffect(() => {
    setValue(formatterWithPrecision(outerValue));
  }, [outerValue, currentUnit, isFocus]);

  // 输入框中实际显示的字符串
  const [value, setValue] = useState(formatterWithPrecision(outerValue));

  const getValueWithLimit = (val: number) => {
    let result = val;
    if (typeof inputProps?.min === 'number') {
      result = Math.max(result, inputProps?.min);
    }

    if (typeof inputProps?.max === 'number') {
      result = Math.min(result, inputProps?.max);
    }

    return result;
  };

  const parser = (input: string | number) => {
    if (typeof input === 'number') {
      return input;
    }

    const regExpString = `^(.*?)(${unitList.map((u: UnitInfo<LengthUnit | AngleUnit>) => u.name).join('|')})?\\s*$`;
    const reg = new RegExp(regExpString);
    const regResult = reg.exec(input);

    if (regResult == null) {
      return outerValue;
    }

    const numPartString = regResult[1];

    const unit = [
      ParameterObjectType.Integer,
      ParameterObjectType.Real,
    ].includes(inputType)
      ? null
      : getUnitParams(regResult[2]);

    const numRegString = /^\s*(-?\d*\.?\d*(e\+\d+)?)\s?$/;
    const numRegResult = numRegString.exec(numPartString);

    let num: number;

    if (numRegResult == null) {
      const evalResult = calculateNumber(numPartString);

      if (typeof evalResult !== 'number') {
        return outerValue;
      }
      num = evalResult as number;
    } else {
      num = Number(numRegResult[1]);
    }

    let numberResult = num * (unit?.multiple ?? 1);

    if (isNaN(numberResult)) {
      return outerValue;
    }

    numberResult = getValueWithLimit(numberResult);

    return inputType === ParameterObjectType.Integer
      ? Math.round(numberResult)
      : numberResult;
  };

  const onStep = (
    _,
    info: {
      type: 'up' | 'down';
    },
  ) => {
    const parsedCurrentVal = parser(value);
    if (typeof parsedCurrentVal === 'string') {
      return;
    }

    const result =
      info.type === 'up'
        ? parsedCurrentVal + currentUnit.multiple
        : parsedCurrentVal - currentUnit.multiple;

    if (
      result < (inputProps.min as number) ||
      result > (inputProps.max as number)
    ) {
      return;
    }

    onChange?.(result);
    setValue(formatterWithPrecision(result));
  };

  const [designTableOpen, setDesignTableOpen] = useState(false);

  const exprId = `${dxid}:${parameterName}`;

  const linkedTable = useParameterLinkedTable(exprId);

  const isEmptyLinkedTable = useMemo(
    () => _.isEmpty(linkedTable),
    [linkedTable],
  );

  const fxBtnId = useId();

  const FX_BTN_EVENT = 'FX-BUTTON-CLICK';

  useEffect(() => {
    const handler = (id: string) => {
      if (id !== fxBtnId) {
        setShowFunctionDialog(false);
      }
    };

    userEvents.on(FX_BTN_EVENT, handler);

    return () => {
      userEvents.off(FX_BTN_EVENT, handler);
    };
  }, []);

  useEffect(() => {
    if (showFunctionDialog) {
      userEvents.emit(FX_BTN_EVENT, fxBtnId);
    }

    onDialogVisibleChange?.(showFunctionDialog);
  }, [showFunctionDialog]);

  const optionRender = (disabled = false) => {
    return (
      <>
        <div className='relative'>
          {isEmptyLinkedTable ? (
            <Button
              disabled={disabled}
              onClick={() => setShowFunctionDialog(true)}
              icon={<FxSvgComponent style={{ padding: '2px' }} />}
            />
          ) : (
            <LinkedParameterInput
              type={ParameterObjectType.Length}
              exprId={exprId}
              expr={outerValue}
              setExpr={onChange}
              showButtonOnly
              open={designTableOpen}
              setOpen={setDesignTableOpen}
              tableId={linkedTable.dxid}
              table={linkedTable.table}
            />
          )}
          {showFunctionDialog && (
            <ExpressionDialog
              type={inputType}
              value={valueIsExpression ? (outerValue as string) : ''}
              onChange={(val) => {
                const isEmpty = /^\s*$/.test(val);
                const result = isEmpty ? null : val;
                if (!valueIsExpression && isEmpty) {
                  return;
                }
                setValue(formatterWithPrecision(result));
                onChange?.(result);
                setShowFunctionDialog(false);
              }}
              onClose={() => {
                setShowFunctionDialog(false);
              }}
            />
          )}
        </div>
      </>
    );
  };

  const onInput = (val: string) => {
    setValue(val);
  };

  return {
    parser,
    formatter,
    onChangeOuter: () => {
      // 由于输入状态下也有精度限制，所以可能出现无改动编辑也会影响源数据的情况
      // 对于编辑后的字符串和源数据字符串对应的情况（也就是输入框字符串不变），不触发onChange
      if (formatter(outerValue, true, INPUT_PRECISION) === value) {
        return;
      }

      const result = parser(value);
      onChange?.(result);
      setValue(formatterWithPrecision(result));
    },
    value,
    currentUnit,
    hasUnit,
    onStep,
    onInput,
    step: 1 / currentUnit.multiple,
    optionRender,
    valueIsExpression,
    showFunctionDialog,
    isEmptyLinkedTable,
    onFocus() {
      setIsFocus(true);
    },
    onBlur() {
      setIsFocus(false);
    },
  };
};
